/**
   vis_server.cpp - Created by Timothy Morey on 1/27/2012.

   This file contains the implementation for the VisServer object.
*/

#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <string>
#include <string.h>

#include "vis_server.h"

VisServer::VisServer(unsigned short port)
  : _ThreadID(-1),
    _Port(port),
    _KeepLooping(true),
    _Image(0),
    _ImageWidth(0),
    _ImageHeight(0),
    _Dirty(false)
{
  pthread_mutex_init(&_ImageMutex, 0);
  this->Start();
}

VisServer::~VisServer()
{
  this->Stop();
}

void VisServer::SetImage(unsigned char* img, unsigned short width, unsigned short height)
{
  // We have to ensure that the server thread can't access the image data while
  // we change it.  It is possible that the owner of the old image buffer will 
  // free it immediately after this call, and if the server thread was halfway 
  // through serving it at that time...
  pthread_mutex_lock(&_ImageMutex);

  _Image = img;
  _ImageWidth = width;
  _ImageHeight = height;
  _Dirty = true;

  pthread_mutex_unlock(&_ImageMutex);
}

void VisServer::SetDirty()
{
  _Dirty = true;
}

void VisServer::SetDirtyAndBlock()
{
  _Dirty = true;

  while(_Dirty)
    pthread_yield();
}

void VisServer::Start()
{
  if(_ThreadID == -1)
    pthread_create(&_ThreadID, 0, VisServer::ThreadEntryPoint, this);
}

void VisServer::Stop()
{
  _KeepLooping = false;

  if(_ThreadID > -1)
    pthread_join(_ThreadID, 0);
}

void* VisServer::ThreadEntryPoint(void* arg)
{
  ((VisServer*)arg)->ThreadLoop();
}

void VisServer::ThreadLoop()
{
  // Create the listener socket
  int listenSocket = socket(AF_INET, SOCK_STREAM, 0);
  if(listenSocket < 0)
  {
    perror("Error creating listenSocket");
    return;
  }

  // Put the socket in non-blocking mode, so that the loop remains responsive.
  // if(0 > fcntl(listenSocket, F_SETFL, O_NONBLOCK))
  // {
  //   perror("Error activating non-blocking mode for listenSocket.");
  //   close(listenSocket);
  //   return;
  // }

  // Bind the socket to our port
  sockaddr_in serverAddr;
  memset(&serverAddr, 0, sizeof(serverAddr));
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_port = htons(_Port);
  serverAddr.sin_addr.s_addr = INADDR_ANY;
  if(0 > bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)))
  {
    perror("Error binding listenSocket");
    close(listenSocket);
    return;
  }

  // Put the socket into listening mode
  if(0 > listen(listenSocket, 5))
  {
    perror("Error putting listenSocket into listening mode");
    close(listenSocket);
    return;
  }

  int imgCount = 0;

  while(_KeepLooping)
  {
    sockaddr_in clientAddr;
    socklen_t clientLen = sizeof(clientAddr);
    int acceptSocket = accept(listenSocket, (sockaddr*)&clientAddr, &clientLen);
    if(acceptSocket >= 0)
      printf("VisServer: accepted client connection\n");

    while (acceptSocket >= 0)
    {
      // We lock here to prevent another thread from changing the image data
      // while we are serving it.
      pthread_mutex_lock(&_ImageMutex);
      
      if(_Image && _Dirty)
      {
	unsigned char* img = _Image;
	unsigned short width = _ImageWidth;
	unsigned short height = _ImageHeight;
	
	printf("[%d] sending a(n) %d x %d image...\n", ++imgCount, width, height);
	
	unsigned char metadata[8];
	((int*)metadata)[0] = width;
	((int*)metadata)[1] = height;
	write(acceptSocket, metadata, 8);
	
	size_t len = width * height * sizeof(unsigned char) * 4;
	size_t written = 0;
	while(written < len)
	{
	  size_t n = send(acceptSocket, img + written, len - written, 0);
	  if(n < 0)
	  {
	    // We got a write error, so kill the connection and look for a new
	    // client.
	    close(acceptSocket);
	    acceptSocket = -1;
	    printf("VisServer: lost client connection\n");
	    break;
	  }
	  else 
	  {
	    written += n;
	  }
	}
	
	_Dirty = false;
      }
      
      pthread_mutex_unlock(&_ImageMutex);

      if(!_Image || !_Dirty)
      {
	char recvBuf[256];
	memset(recvBuf, 0, 256);
	int received = recv(acceptSocket, recvBuf, 256, MSG_DONTWAIT);
	if(received > 0 && 
	   0 == strcmp(recvBuf, "close-session"))
	{
	  printf("close-session received\n");
	  close(acceptSocket);
	  acceptSocket = -1;
	  printf("VisServer: client closed session\n");
	}
      }
    }

    // Our client connection is now closed, we will want the next client to get
    // the current frame, so set the dirty flag:
    _Dirty = true;
  }

  close(listenSocket);
}
